#pragma once

#include <queue>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <stdexcept>

template <typename T>
class BlockingQueue {
public:  

    explicit BlockingQueue(size_t capacity);

    // 阻塞入队
    void enqueue(T item);

    // 尝试入队（非阻塞），队满返回false
    bool try_enqueue(T item);

    // 限时尝试入队。指定时间阻塞，超时仍无法入队，则返回false
    bool try_enqueue_for(T item, std::chrono::milliseconds timeout);

    // 阻塞出队。当队列为空时，会阻塞线程直到队列有元素。
    T dequeue();

    // 尝试出队（非阻塞）。队空返回false
    bool try_dequeue(T& item);

    // 限时尝试出队，指定时间内阻塞，超时仍无法出队，则返回false
    bool try_dequeue_for(T& item, std::chrono::milliseconds timeout);

    // 一些状态方法
    size_t size() const noexcept;
    bool empty() const noexcept;
    bool full() const noexcept;

    // 关闭队列，唤醒等待队列
    void shutdown() noexcept;
    
private:
    std::queue<T> m_queue;
    mutable std::mutex m_mutex; // const 方法需要mutable  
    std::condition_variable m_notEmptyCv; 
    std::condition_variable m_notFullCv;
    size_t m_capacity;
    bool m_running; 
};



template <typename T>
BlockingQueue<T>::BlockingQueue(size_t capacity)
    : m_capacity(capacity)
    , m_running(true) {}


template <typename T>
void BlockingQueue<T>::enqueue(T item) {
    std::unique_lock<std::mutex> lock(m_mutex);

    m_notFullCv.wait(lock, [this]{ return !m_running || m_queue.size() < m_capacity; });

    if (!m_running) {
        throw std::runtime_error("BlockingQueue is shutting down.");
    }
    m_queue.push(std::move(item));
    m_notEmptyCv.notify_one();
}


template <typename T>
bool BlockingQueue<T>::try_enqueue(T item) {
    std::unique_lock<std::mutex> lock(m_mutex);
    if (!m_running || m_queue.size() >= m_capacity) {
        return false;
    }
    m_queue.push(std::move(item));
    m_notEmptyCv.notify_one();
    return true;
}


template <typename T>
bool BlockingQueue<T>::try_enqueue_for(T item, std::chrono::milliseconds timeout) {
    std::unique_lock<std::mutex> lock(m_mutex);
    if (!m_notFullCv.wait_for(lock, timeout, [this] { !m_running || m_queue.size() < m_capacity; })) {
        return false;
    }

    if (!m_running) {
        throw  std::runtime_error("BlockingQueue is shutting down.");
    }

    m_queue.push(std::move(item));
    m_notEmptyCv.notify_one();
    return true;
}


template <typename T>
T BlockingQueue<T>::dequeue() {
    std::unique_lock<std::mutex> lock(m_mutex);
    m_notEmptyCv.wait(lock, [this]{ return !m_running || !m_queue.empty(); });

    if (!m_running && m_queue.empty()) {
        throw std::runtime_error("BlockingQueue is shutting down and empty.");
    }

    T item = std::move(m_queue.front());
    m_queue.pop();
    m_notFullCv.notify_one();
    return item;
}
   

template <typename T>
bool BlockingQueue<T>::try_dequeue(T& item) {
    std::unique_lock<std::mutex> lock(m_mutex);
    if (!m_running || m_queue.empty()) {
        return false;
    }
    item = std::move(m_queue.front());
    m_queue.pop();
    m_notFullCv.notify_one();
    
    return true;
}


template <typename T>
bool BlockingQueue<T>::try_dequeue_for(T& item, std::chrono::milliseconds timeout) {
    std::unique_lock<std::mutex> lock(m_mutex);
    if (!m_notEmptyCv.wait_for(lock, timeout, [this]{ return !m_running || !m_queue.empty(); })) {
        return false;
    }

    if (!m_running && m_queue.empty()) {
        return false;
    }

    item = std::move(m_queue.front());
    m_queue.pop();
    m_notFullCv.notify_one();
    return true;
}


template <typename T>
size_t BlockingQueue<T>::size() const noexcept {
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_queue.size();
}


template <typename T>
bool BlockingQueue<T>::full() const noexcept {
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_queue.size() >= m_capacity;
}


template <typename T>
bool BlockingQueue<T>::empty() const noexcept {
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_queue.empty();
}


template <typename T>
void BlockingQueue<T>::shutdown() noexcept {
    std::unique_lock<std::mutex> lock(m_mutex);
    m_running = false;
    m_notFullCv.notify_all();
    m_notEmptyCv.notify_all();
}

